<?php

class Httper {

    public $cookie_file = '/tmp/httper_cookie.txt';
    public $headers = array();
    public $options = array();
    public $referer = '';
    public $user_agent = '';

    protected $error = '';

    public function __construct() {
        $this->user_agent = 'Mozilla/5.0 (X11; U; Linux i686; en; rv:1.8.1.13) Gecko/20080325 Firefox/2.0.0.13';
    }

    public function delete($url, $vars = array()) {
        return $this->request('DELETE', $url, $vars);
    }

    public function error() {
        return $this->error;
    }

    public function get($url, $vars = array()) {
        if (!empty($vars)) {
            $url .= (stripos($url, '?') !== false) ? '&' : '?';
            $url .= http_build_query($vars);
        }
        return $this->request('GET', $url);
    }

    public function post($url, $vars = array()) {
        return $this->request('POST', $url, $vars);
    }

    public function put($url, $vars = array()) {
        return $this->request('PUT', $url, $vars);
    }

    protected function request($method, $url, $vars = array()) {
        $handle = curl_init();

        // Set some default CURL options
        curl_setopt($handle, CURLOPT_COOKIEFILE, $this->cookie_file);
        curl_setopt($handle, CURLOPT_COOKIEJAR, $this->cookie_file);
        curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($handle, CURLOPT_HEADER, true);
        curl_setopt($handle, CURLOPT_REFERER, $this->referer);
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($handle, CURLOPT_URL, $url);
        curl_setopt($handle, CURLOPT_USERAGENT, $this->user_agent);

        // Format custom headers for this request and set CURL option
        $headers = array();
        foreach ($this->headers as $key => $value) {
            $headers[] = $key.': '.$value;
        }
        curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);

		//curl_setopt($handle, CURLOPT_VERBOSE, true);

        // Determine the request method and set the correct CURL option
        switch ($method) {
            case 'GET':
                curl_setopt($handle, CURLOPT_HTTPGET, true);
                break;
            case 'POST':
                curl_setopt($handle, CURLOPT_POST, true);
				curl_setopt($handle, CURLOPT_POSTFIELDS, $vars);
                break;
            default:
                curl_setopt($handle, CURLOPT_CUSTOMREQUEST, $method);
        }

        // Set any custom CURL options
        foreach ($this->options as $option => $value) {
            curl_setopt($handle, constant('CURLOPT_'.str_replace('CURLOPT_', '', strtoupper($option))), $value);
        }

        $response = curl_exec($handle);
        if ($response) {
            $response = new HttperResponse($response);
        } else {
            $this->error = curl_errno($handle).' - '.curl_error($handle);
        }
        curl_close($handle);

        return $response;
    }

    // multi-way concurrent operation 
    /**
     * \param $urls -- a list of urls, in array. 
     */
    public    function    mget($urls, $timeout = -1) 
    {
        return    $this->mrequest('GET', $urls, $timeout);
    }
    
    /**
     * \param $method -- HTTP method name, such as GET, POST, etc
     * \param $urls -- a list of urls, in array. 
     */
    public    function    mrequest($method, $urls, $timeout = -1)
    {
        $responses = array();
        $chs = array();
        $cmh = curl_multi_init();

        $time_start = microtime(true);
        $time_deadline = $time_start + $timeout;
        
        // prepare 
        foreach ($urls as $url) {        
            $handle = curl_init();
    
            // Set some default CURL options
            curl_setopt($handle, CURLOPT_COOKIEFILE, $this->cookie_file);
            curl_setopt($handle, CURLOPT_COOKIEJAR, $this->cookie_file);
            curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($handle, CURLOPT_HEADER, false);
            curl_setopt($handle, CURLOPT_POSTFIELDS, $vars);
            curl_setopt($handle, CURLOPT_REFERER, $this->referer);
            curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($handle, CURLOPT_URL, $url);
            curl_setopt($handle, CURLOPT_USERAGENT, $this->user_agent);
    
            // Format custom headers for this request and set CURL option
            $headers = array();
            foreach ($this->headers as $key => $value) {
                $headers[] = $key.': '.$value;
            }
            curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
    
            // Determine the request method and set the correct CURL option
            switch ($method) {
                case 'GET':
                    curl_setopt($handle, CURLOPT_HTTPGET, true);
                    break;
                case 'POST':
                    curl_setopt($handle, CURLOPT_POST, true);
                    break;
                default:
                    curl_setopt($handle, CURLOPT_CUSTOMREQUEST, $method);
            }
    
            // Set any custom CURL options
            foreach ($this->options as $option => $value) {
                curl_setopt($handle, constant('CURLOPT_'.str_replace('CURLOPT_', '', strtoupper($option))), $value);
            }
            
            $chs[] = $handle;
            curl_multi_add_handle($cmh, $handle);
        }    // for prepare 

        // running
        $running = false;
        do {
            curl_multi_exec($cmh, $running);
            if ($running > 0) {
                usleep(10000);
                if ($timeout > 0) {
                    $now = microtime(true);
                    if ( $now > $time_deadline ) {
                        break;    // timeout 
                    } 
                }
            }
        } while ($running > 0);        
        
        // post-process
        $time_last = microtime(true) - $time_start;
        $count_urls = count($urls);
        for ($i=0; $i<$count_urls; $i++) {
            $url = $urls[$i];
            $handle = $chs[$i];
            $body = curl_multi_getcontent($handle);

            $code = curl_getinfo($handle, CURLINFO_HTTP_CODE);
            if (!$code) $code = 555;
            $time = curl_getinfo($handle, CURLINFO_TOTAL_TIME);
            if (!$time) $time = sprintf('%.3f', $time_last);
            $length = curl_getinfo($handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
            if (!$length) {
                // if length == 0, means download does NOT completed.
                $length = 0;

                //if ($body) $length = strlen($body);
                //else $length = 0;
            }
            $type = curl_getinfo($handle, CURLINFO_CONTENT_TYPE);
            if (!$type) $type = 'type/unknown';
            
            curl_multi_remove_handle($cmh, $handle);
            curl_close($handle);
            
            $response = new HttperResponseX($url, $body);
            $response->code = $code;
            $response->time = $time;
            $response->length = $length;
            $response->type = $type;
            $responses[] = $response;
        }
        
        curl_multi_close($cmh);
        return    $responses;
    }
}

class    HttperResponseX
{
    public    $url = '';
    public    $body = '';
    public    $headers = array( 'code' => 200 );
    
    public    function    __construct($url, $body = '') 
    {
        $this->url = $url;
        $this->body = $body;
    }
    
    public    function    __set($key, $value) 
    {
        $this->headers[ $key ] = $value;
    }
    
    public    function    code()
    {
        return    $this->headers['code'];
    }
    
    public    function    status_line()
    {
        return    sprintf("HTTP/1.1 %d", $this->headers['code']);
    }
}

class    HttperResponse 
{

    public $body = '';
    public $headers = array();

    public function __construct($response) 
    {
        // Extract headers from response
        $pattern = '#HTTP/\d\.\d.*?$.*?\r\n\r\n#ims';
        preg_match_all($pattern, $response, $matches);
        $headers = split("\r\n", str_replace("\r\n\r\n", '', array_pop($matches[0])));

        // Extract the version and status from the first header
        $version_and_status = array_shift($headers);
        preg_match('#HTTP/(\d\.\d)\s(\d\d\d)\s(.*)#', $version_and_status, $matches);
        $this->headers['Http-Version'] = $matches[1];
        $this->headers['Status-Code'] = $matches[2];
        $this->headers['Status'] = $matches[2].' '.$matches[3];

        // Convert headers into an associative array
        foreach ($headers as $header) {
            preg_match('#(.*?)\:\s(.*)#', $header, $matches);
            $this->headers[$matches[1]] = $matches[2];
        }

        // Remove the headers from the response body
        $this->body = preg_replace($pattern, '', $response);
    }

	public function	code() 
	{
		if (isset($this->headers['Status-Code'])) {
			return	$this->headers['Status-Code'];
		}
		
		return	555;
	}
	
	private function status_line() 
	{
		if (isset($this->headers['Status'])) {
			return	$this->headers['Status'];
		}
	
		return	false;
	}
	
    public function __toString() 
    {
        return $this->body;
    }

}

?>
